/**
 * 
 */
package com.alphasystem.commerce.common.util;

import java.security.MessageDigest;

/**
 * @author sali
 * 
 */
public final class IdentitySHA {

	private static final int DEFAULT_LENGTH = 32;

	private static byte[] DEFAULT_SALT = new String("JAVA").getBytes();

	private static String hexits = "0123456789abcdef";

	/**
	 * Combine two byte arrays
	 * 
	 * @param l
	 *            first byte array
	 * @param r
	 *            second byte array
	 * @return byte[] combined byte array
	 */
	private static byte[] concatenate(byte[] l, byte[] r) {
		byte[] b = new byte[l.length + r.length];
		System.arraycopy(l, 0, b, 0, l.length);
		System.arraycopy(r, 0, b, l.length, r.length);
		return b;
	}

	/**
	 * Convert Hex String to byte array
	 * 
	 * @param s
	 *            string to convert
	 * @return byte array
	 */

	@SuppressWarnings("unused")
	private static byte[] fromHex(String s) {
		s = s.toLowerCase();
		byte[] b = new byte[(s.length() + 1) / 2];
		int j = 0;
		int h;
		int nybble = -1;
		for (int i = 0; i < s.length(); ++i) {
			h = hexits.indexOf(s.charAt(i));
			if (h >= 0) {
				if (nybble < 0) {
					nybble = h;
				} else {
					b[j++] = (byte) ((nybble << 4) + h);
					nybble = -1;
				}
			}
		}
		if (nybble >= 0) {
			b[j++] = (byte) (nybble << 4);
		}
		if (j < b.length) {
			byte[] b2 = new byte[j];
			System.arraycopy(b, 0, b2, 0, j);
			b = b2;
		}
		return b;
	}

	public static void main(String[] args) {
		IdentitySHA sha = new IdentitySHA();
		String password = "password";
		String pw = sha.createShortDigest(password);
		System.out.println("createShortDigest: " + pw + " " + pw.length());
		pw = sha.createDigest(password);
		System.out.println("createDigest: " + pw + " " + pw.trim().length());
		pw = sha.createHL7Digest(password, 32);
		System.out.println("createHL7Digest: " + pw + " " + pw.length());
	}

	/**
	 * split a byte array in two
	 * 
	 * @param src
	 *            byte array to be split
	 * @param n
	 *            element at which to split the byte array
	 * @return byte[][] two byte arrays that have been split
	 */
	private static byte[][] split(byte[] src, int n) {
		byte[] l, r;
		if (src == null || src.length <= n) {
			l = src;
			r = new byte[0];
		} else {
			l = new byte[n];
			r = new byte[src.length - n];
			System.arraycopy(src, 0, l, 0, n);
			System.arraycopy(src, n, r, 0, r.length);
		}
		byte[][] lr = { l, r };
		return lr;
	}

	/**
	 * Convert byte array to hex character string
	 * 
	 * @param block
	 *            byte array to convert to hexString
	 * @return String representation of byte arrayf
	 */
	@SuppressWarnings("unused")
	private static String toHex(byte[] block) {
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < block.length; ++i) {
			buf.append(hexits.charAt((block[i] >>> 4) & 0xf));
			buf.append(hexits.charAt(block[i] & 0xf));
		}
		return buf + "";
	}

	private MessageDigest sha = null;

	/**
	 * public constructor
	 */
	public IdentitySHA() {
		try {
			sha = MessageDigest.getInstance("SHA-1");
		} catch (java.security.NoSuchAlgorithmException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	/**
	 * Check Digest against identity
	 * 
	 * @param digest
	 *            is digest to be checked against
	 * @param identity
	 *            to be checked
	 * @return true if digest and identity are equal, false otherwise
	 */
	public boolean checkDigest(String digest, String identity) {
		if (digest.regionMatches(true, 0, "{SHA}", 0, 5)) {
			digest = digest.substring(5); // ignore the label
		} else if (digest.regionMatches(true, 0, "{SSHA}", 0, 6)) {
			digest = digest.substring(6); // ignore the label
		}

		byte[][] hs = split(Base64.decode(digest), 20);
		byte[] hash = hs[0];
		byte[] salt = hs[1];

		sha.reset();
		sha.update(identity.getBytes());
		sha.update(salt);
		byte[] pwhash = sha.digest();

		return MessageDigest.isEqual(hash, pwhash);
	}

	/**
	 * @param digest
	 * @param salt
	 * @param identity
	 * @return
	 */
	public boolean checkShortDigest(String digest, byte[] salt, String identity) {
		int length = digest.length();
		sha.reset();
		sha.update(identity.getBytes());
		sha.update(salt);
		byte[] pwhash = sha.digest();
		String result = new String(Base64.encode(concatenate(pwhash, salt)))
				.substring(0, length);
		return digest.equals(result);
	}

	/**
	 * @param digest
	 * @param identity
	 * @return
	 */
	public boolean checkShortDigest(String digest, String identity) {
		return checkShortDigest(digest, DEFAULT_SALT, identity);
	}

	/**
	 * Create Digest for each input identity
	 * 
	 * @param salt
	 *            to set the base for the encryption
	 * @param identity
	 *            to be encrypted
	 */

	public String createDigest(byte[] salt, String identity) {
		String label = (salt.length > 0) ? "{SSHA}" : "{SHA}";

		sha.reset();
		sha.update(identity.getBytes());
		sha.update(salt);
		byte[] pwhash = sha.digest();
		return label + new String(Base64.encode(concatenate(pwhash, salt)));
	}

	/**
	 * @param identity
	 * @return
	 */
	public String createDigest(String identity) {
		return createDigest(DEFAULT_SALT, identity);
	}

	/**
	 * Create short version of a digest by taking the first {length} bytes of
	 * the hash.
	 * 
	 * @param salt
	 *            Base for the encryption
	 * @param identity
	 *            Value to be encrypted
	 * @param length
	 *            Length of extract to keep
	 * @return Digest string
	 */
	public String createHL7Digest(byte[] salt, String identity, int length) {
		sha.reset();
		sha.update(identity.getBytes());
		sha.update(salt);
		byte[] pwhash = sha.digest();
		StringBuffer result = new StringBuffer(new String(
				Base64.encode(concatenate(pwhash, salt))));
		result.setLength(length);
		// Don't allow carets ^ or / in the result
		for (int i = 0; i < length; i++) {
			if (result.charAt(i) == '^')
				result.setCharAt(i, 'A');
			// if (result.charAt(i) == '/') result.setCharAt(i, 'B');
		}
		return result.toString();
	}

	/**
	 * @param identity
	 * @param length
	 * @return
	 */
	public String createHL7Digest(String identity, int length) {
		return createHL7Digest(DEFAULT_SALT, identity, length);
	}

	/**
	 * Create short version of a digest by taking the first {length} bytes of
	 * the hash.
	 * 
	 * @param salt
	 *            Base for the encryption
	 * @param identity
	 *            Value to be encrypted
	 * @param length
	 *            Length of extract to keep
	 * @return Digest string
	 */
	public String createShortDigest(byte[] salt, String identity, int length) {
		sha.reset();
		sha.update(identity.getBytes());
		sha.update(salt);
		byte[] pwhash = sha.digest();
		return new String(Base64.encode(concatenate(pwhash, salt))).substring(
				0, length);
	}

	public String createShortDigest(String identity) {
		return createShortDigest(DEFAULT_SALT, identity, DEFAULT_LENGTH);
	}

	/**
	 * @param identity
	 * @param length
	 * @return
	 */
	public String createShortDigest(String identity, int length) {
		return createShortDigest(DEFAULT_SALT, identity, length);
	}
}
